/*
 * Copyright (C) 2008,2009,2010,2011  Paulo Cesar Pereira de Andrade.
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * Authors:
 *	Paulo Cesar Pereira de Andrade
 */

#include "exl.h"

#define consume()							\
    do {								\
	if (lah_token != tok_eof)					\
	    lah_token = tok_eof;					\
	else								\
	    eread_error(null, "consuming unread token");		\
    } while (0)

/*
 * Prototypes
 */
static void
annotate(eint32_t line, eobject_t name, eobject_t object);

static inline etoken_t
statement_noeof(void);

static inline etoken_t
expression_noeof(void);

static etoken_t
expression_rvalue(void);

static inline etoken_t
precedence_noeof(void);

static inline etoken_t
unary_noeof(void);

static etoken_t
unary_rvalue(void);

static inline etoken_t
primary_noeof(void);

static etoken_t
structure(etoken_t token);

static etoken_t
statement(void);

static etoken_t
expression(void);

static etoken_t
precedence(void);

static etoken_t
expression_group(etoken_t state);

static etoken_t
binary_assign(void);

static etoken_t
binary_left(etoken_t token);

static void
binary_right(etoken_t token);

static etoken_t
unary(void);

static ebool_t
dynamic_vector_p(eobject_t list);

static etoken_t
unary_vector(void);

static etoken_t
unary_field(void);

static etoken_t
unary_list(void);

static etoken_t
unary_pointer(void);

static etoken_t
unary_string(void);

static etoken_t
primary(void);

static etoken_t
tokenize(void);

static etoken_t
tokenize_eval(etoken_t token, evalue_t *arg0, evalue_t *arg1);

/*
 * Initialization
 */
esymbol_t	*symbol_null, *symbol_true, *symbol_false;
esymbol_t	*symbol_new, *symbol_this, *symbol_throw;
ehash_t *hash_annotation;

static struct {
    char	*name;
    etoken_t	 token;
} keywords[] = {
    /* macros */
    { "__FILE__",	tok_FILE	},
    { "__LINE__",	tok_LINE	},
    { "__FUNCTION__",	tok_FUNCTION	},
    { "__VA_ARGS__",	tok_VA_ARGS	},
    { "__DATE__",	tok_DATE	},
    { "__TIME__",	tok_TIME	},
    { "__COUNTER__",	tok_COUNTER	},
    { "#",		tok_stringify	},
    { "##",		tok_concat	},

    /* punctuation */
    { "(",		tok_list 	},
    { ")",		tok_elist	},
    { "[",		tok_vector	},
    { "]",		tok_evector	},
    { "{",		tok_brace	},
    { "}",		tok_ebrace	},
    { ";",		tok_progn	},
    { ":",		tok_collon	},
    { ",",		tok_comma	},
    { ".",		tok_field	},
    { "...",		tok_ellipsis	},

    /* assignment */
    { "=",		tok_set		},
    { "&=", 		tok_andset	},
    { "|=", 		tok_orset	},
    { "^=", 		tok_xorset	},
    { "<<=",		tok_mul2set	},
    { ">>=",		tok_div2set	},
    { "<<<=",		tok_shlset	},
    { ">>>=",		tok_shrset	},
    { "+=", 		tok_addset	},
    { "-=", 		tok_subset	},
    { "*=", 		tok_mulset	},
    { "/=", 		tok_divset	},
    { "\\=", 		tok_trunc2set	},
    { "%=", 		tok_remset	},

    /* logic operators */
    { "&&", 		tok_andand	},
    { "||", 		tok_oror	},

    /* number comparison */
    { "!=", 		tok_ne		},
    { "<",		tok_lt		},
    { "<=", 		tok_le		},
    { "==", 		tok_eq		},
    { ">",		tok_gt		},
    { ">=", 		tok_ge		},

    /* bit operations */
    { "&",		tok_and		},
    { "|",		tok_or		},
    { "^",		tok_xor		},

    /* arithmetic shift */
    { "<<", 		tok_mul2	},
    { ">>", 		tok_div2	},
    { "<<<", 		tok_shl		},
    { ">>>", 		tok_shr		},

    /* addition and subtraction */
    { "+",		tok_add		},
    { "-",		tok_sub		},

    /* multiplication, division and modulo */
    { "*",		tok_mul		},
    { "/",		tok_div		},
    { "\\",		tok_trunc2	},
    { "%",		tok_rem		},

    /* left side unary operations */
    { "++", 		tok_inc		},
    { "--", 		tok_dec		},
    { "!",		tok_not		},
    { "~",		tok_com		},
    { "sizeof",		tok_sizeof	},
    { "typeof",		tok_typeof	},
    { "new",		tok_new		},
    { "rankof",		tok_rankof	},

    /* function like binary operations */
    { "atan2", 		tok_atan2	},
    { "pow", 		tok_pow		},
    { "hypot",		tok_hypot	},
    { "complex",	tok_complex	},
    { "subtypeof",	tok_subtypeof	},
    { "renew",		tok_renew	},

    /* function like unary operations */
    { "signbit",	tok_signbit	},
    { "signum",	 	tok_signum	},
    { "rational",	tok_rational	},
    { "integer_p",	tok_integer_p	},
    { "rational_p",	tok_rational_p	},
    { "float_p",	tok_float_p	},
    { "real_p",	 	tok_real_p	},
    { "complex_p",	tok_complex_p	},
    { "number_p",	tok_number_p	},
    { "finite_p",	tok_finite_p	},
    { "inf_p",	 	tok_inf_p	},
    { "nan_p",	 	tok_nan_p	},
    { "num", 		tok_num		},
    { "den", 		tok_den		},
    { "real", 		tok_real	},
    { "imag", 		tok_imag	},
    { "arg", 		tok_arg		},
    { "conj", 		tok_conj	},
    { "floor", 		tok_floor	},
    { "trunc", 		tok_trunc	},
    { "round", 		tok_round	},
    { "ceil", 		tok_ceil	},
    { "abs", 		tok_abs		},
    { "sqrt", 		tok_sqrt	},
    { "cbrt",		tok_cbrt	},
    { "sin",		tok_sin		},
    { "cos",		tok_cos		},
    { "tan",		tok_tan		},
    { "asin",		tok_asin	},
    { "acos",		tok_acos	},
    { "atan",		tok_atan	},
    { "sinh",		tok_sinh	},
    { "cosh",		tok_cosh	},
    { "tanh",		tok_tanh	},
    { "asinh",		tok_asinh	},
    { "acosh",		tok_acosh	},
    { "atanh",		tok_atanh	},
    { "proj",		tok_proj	},
    { "exp",		tok_exp		},
    { "log",		tok_log		},
    { "log2",		tok_log2	},
    { "log10",		tok_log10	},

    /* ternary operator */
    { "?",		tok_ternary	},

    { "this",		tok_this	},

    /* keywords */
    { "if",		tok_if		},
    { "else",		tok_else	},
    { "goto",		tok_goto	},
    { "return",		tok_return	},

    { "switch",		tok_switch	},
    { "case",		tok_case	},
    { "default",	tok_default	},
    { "break",		tok_break	},

    { "for", 		tok_for		},
    { "do",		tok_do		},
    { "while",		tok_while	},
    { "continue",	tok_continue	},

    { "try", 		tok_try		},
    { "catch",		tok_catch	},
    { "throw",		tok_throw	},
    { "always",		tok_always	},

    { "struct",		tok_struct	},
    { "class",		tok_class	},
    { "union",		tok_union	},
    { "typedef",	tok_typedef	},
};

static etoken_t		lah_token;
static eobject_t	lah_value;

/*
 * Implementation
 */
void
init_parser(void)
{
    eint32_t	 i;
    esymbol_t	*symbol;

    /* Declare language keywords / operators */
    for (i = 0; i < esize(keywords); i++) {
	symbol = enew_symbol(ht_language,
			     eget_string((euint8_t *)keywords[i].name,
					 strlen(keywords[i].name)));
	symbol->a_token = true;
	symbol->value = efixnum(keywords[i].token);
    }

    symbol = enew_symbol(ht_language, eget_string((euint8_t *)"null", 4));
    symbol->value = null;
    symbol->a_special = true;

    symbol = enew_symbol(ht_language, eget_string((euint8_t *)"true", 4));
    symbol->value = efixnum(1);
    symbol->a_special = true;

    symbol = enew_symbol(ht_language, eget_string((euint8_t *)"false", 5));
    symbol->value = efixnum(0);
    symbol->a_special = true;

    symbol = enew_symbol(ht_language, eget_string((euint8_t *)"nan", 3));
    efloat(&symbol->value, NAN);
    symbol->a_special = true;

    symbol = enew_symbol(ht_language, eget_string((euint8_t *)"inf", 3));
    efloat(&symbol->value, INFINITY);
    symbol->a_special = true;

    symbol = enew_symbol(ht_language, eget_string((euint8_t *)"I", 1));
    mpq_set_ui(thr_qr, 0, 1);
    mpq_set_ui(thr_qi, 1, 1);
    ecqq(&symbol->value, &tmain->qq);
    symbol->a_special = true;

    symbol_new = eget_symbol(ht_language, eget_string((euint8_t *)"new", 3));
    symbol_this = eget_symbol(ht_language, eget_string((euint8_t *)"this", 4));
    symbol_throw = eget_symbol(ht_language, eget_string((euint8_t *)"throw", 5));

    lah_token = tok_eof;

    enew_hash((eobject_t *)&hash_annotation, H_pointer, 5);
    eadd_root((eobject_t *)&hash_annotation);

    /* make eparse() logic simpler, to keep result gc protected */
    v_push(null);
}

eobject_t
eparse(void)
{
    v_dec();

    eclr_hash(hash_annotation);

    if (statement() == tok_eof)
	return (object_eof);

#if DEBUG
    if (tmain->gsi != 1)
	eread_error(null, "internal error (parser stack unbalanced)");
#endif

    return (v_top());
}

etoken_t
ekind_of(etoken_t token)
{
    switch (token) {
	case tok_eof ... tok_concat:
	    return (tok_type);
	case tok_list ... tok_ellipsis:
	    return (tok_list);
	case tok_set ... tok_remset:
	    return (tok_set);
	case tok_andand ... tok_oror:
	    return (tok_logop);
	case tok_ne ... tok_gt:
	    return (tok_logcmp);
	case tok_and ... tok_xor:
	    return (tok_bitop);
	case tok_mul2 ... tok_shr:
	    return (tok_shift);
	case tok_add ... tok_sub:
	    return (tok_addsub);
	case tok_mul ... tok_rem:
	    return (tok_muldivrem);
	case tok_sizeof ... tok_log10:
	    return (tok_unary);
	case tok_inc ... tok_postdec:
	    return (tok_incdec);
	case tok_ternary ... tok_this:
	    return (tok_ternary);
	case tok_if ... tok_always:
	    return (tok_if);
	case tok_for ... tok_while:
	    return (tok_loop);
	case tok_struct ... tok_typedef:
	    return (tok_attribute);
	default:
	    return (tok_composed);
    }
}

static void
annotate(eint32_t line, eobject_t name, eobject_t object)
{
    eentry_t	*entry;
    eobject_t	 integer;

    /*   Very simple schema to report input errors.
     *   A "complete" error reporting would require storing every
     * "token" like object is a structure with line, column, filename
     * and actual value information. But instead, use a simpler,
     * faster, and lower memory requirements method, by associating
     * expressions/statements with a filename and linenumber data
     * structure (that uses eentry fields to store the information).
     *   The information is only valid until eparse() is called again,
     * but should be enough to report proper linenumbers on errors,
     * when parsing function bodies, structure definitions and static
     * data initializations.
     */
    v_check(1);
    enew(&entry, entry);
    v_push(entry);
    entry->name  = object;
    einteger(&integer, line);
    entry->value = econs(integer, name);
    eput_hash(hash_annotation, entry);
    v_dec();
}

static inline etoken_t
lookahead(void)
{
    if (lah_token == tok_eof) {
	if (lah_value != object_eof && (lah_token = primary()) != tok_eof)
	    lah_value = v_pop();
	else
	    lah_value = object_eof;
    }

    return (lah_token);
}

static inline etoken_t
statement_noeof(void)
{
    etoken_t	token;

    if ((token = statement()) == tok_eof)
	eread_error(null, "unexpected end of file");

    return (token);
}

static inline etoken_t
expression_noeof(void)
{
    etoken_t	token;

    if ((token = expression()) == tok_eof)
	eread_error(null, "unexpected end of file");

    return (token);
}

static etoken_t
expression_rvalue(void)
{
    etoken_t	token;

    token = expression_noeof();
    switch (token) {
	case tok_number:
	case tok_symbol:
	case tok_expression:
	case tok_string:
	case tok_function:
	case tok_pointer:
	case tok_address:
	case tok_typeof:
	case tok_type:
	    break;
	default:
	    eread_error(null, "expecting expression");
    }

    return (token);
}

static inline etoken_t
precedence_noeof(void)
{
    etoken_t	token;

    if ((token = precedence()) == tok_eof)
	eread_error(null, "unexpected end of file");

    return (token);
}

static inline etoken_t
unary_noeof(void)
{
    etoken_t	token;

    if ((token = unary()) == tok_eof)
	eread_error(null, "unexpected end of file");

    return (token);
}

static etoken_t
unary_rvalue(void)
{
    etoken_t	token;

    token = unary_noeof();
    switch (token) {
	case tok_number:
	case tok_symbol:
	case tok_string:
	case tok_expression:
	case tok_function:
	case tok_pointer:
	case tok_typeof:
	case tok_type:
	    break;
	default:
	    eread_error(null, "expecting expression");
    }

    return (token);
}

static inline etoken_t
primary_noeof(void)
{
    etoken_t	token;

    if ((token = primary()) == tok_eof)
	eread_error(null, "unexpected end of file");

    return (token);
}

static etoken_t
structure(etoken_t token)
{
    etag_t	*tag;
    etoken_t	 next;
    esymbol_t	*symbol;
    erecord_t	*record;

    /* declare type */
    next = expression_noeof();
    switch (next) {
	case tok_type:
	    tag = v_top();
	    if (tag == null)			goto record_fail;
	    record = tag->data;
	    if (tag->type != token - tok_struct + tag_struct)
		goto tag_fail;
	    if (record->fields->count)
		eread_error(null, "'%s' '%p' redeclared",
			    erecord_token_string(token), record->name->name);
	    if (lookahead() == tok_collon) {
		consume();
		goto superclass;
	    }
	    break;
	case tok_symbol:
	    symbol = v_top();
	    enew_record(symbol, token - tok_struct + t_struct);
	    v_top() = symbol->value;
	    break;
	case tok_declaration:
	    tag = ecar(v_top());
	    if (tag == null)			goto record_fail;
	    /* record set for error message */
	    record = tag->data;
	    if (tag->type != token - tok_struct + tag_struct)
		goto tag_fail;
	    break;
	case tok_label:
	    symbol = ecadr(v_top());
	    if (!esymbol_p(symbol))
		eread_error(null, "'%s' name is not a symbol",
			    erecord_token_string(token));
	    record = enew_record(symbol, token - tok_struct + t_struct);
	    v_top() = symbol->value;
	superclass:
	    if (lookahead() == tok_type) {
		primary();
		tag = v_pop();
		if (tag == null || !etag_p(tag))
		    eread_error(null, "expecting type");
		/* eadd_record checks if record types match */
		eadd_record(record, tag->data);
	    }
	    /* if no new fields added */
	    if (lookahead() != tok_brace)
		eend_record(record);
	    break;
	default:
	record_fail:
	    eread_error(null, "expecting type or symbol");
    }

    return (next);
tag_fail:
    switch (etype(record)) {
	case t_struct: case t_class: case t_union:
	    eread_error(null, "'%p' redeclared as a different object",
			record->name->name);
	default:
	    goto record_fail;
    }
}

static etoken_t
statement(void)
{
    eint32_t	 line;
    eobject_t	 name;
    etoken_t	 token;
    eobject_t	 append;
    eobject_t	 object;

    token = lookahead();
    line = input->line;
    name = input->name;
    switch (token) {
	case tok_eof:
	    return (token);
	case tok_brace:
	    /*   Don't want expression() to parse braces as a list of
	     * expressions, but to locally parse braces as a list of
	     * statements. */
	    primary();
	    break;
	default:
	    token = expression();
	    break;
    }
    switch (token) {
	case tok_label:
	    object = econs(efixnum(tok_progn), econs_nil(v_top()));
	    v_top() = object;
	    while ((token = statement()) == tok_label) {
		object = ecdr(object);
		erplacd(object, econs_nil(v_pop()));
	    }
	    if (token != tok_eof) {
		object = ecdr(object);
		erplacd(object, econs_nil(v_pop()));
		token = tok_expression;
	    }
	    else
		token = tok_label;
	    break;
	case tok_ctor:
	    switch (lookahead()) {
		case tok_progn:
		    consume();
		    break;
		case tok_brace:
		    object = v_top();
		    statement();
		    erplacd(ecdr(object), econs_nil(v_pop()));
		    break;
		default:
		    eread_error(null, "expecting ';' or '{ ... }'");
	    }
	    break;
	case tok_declaration:
	    object = v_top();
	declaration:
	    switch (lookahead()) {
		case tok_progn:
		    consume();
		    break;
		case tok_comma:
		    consume();
		    if (expression_group(tok_code) != tok_progn)
			eread_error(null, "expecting 'expression+ ;'");
		    consume();
		    erplacd(ecdr(object), v_pop());
		    break;
		case tok_brace:
		    statement();
		    /* format as:
		     *	(type ((name (alist)) body))
		     * to differentiate from prototypes, that are formated as:
		     *	(type (name alist))
		     * and can also be formated as:
		     *	(type (name0 alist0) (name1 alist1))
		     */
		    erplacd(object,
			    econs_nil(econs(ecadr(object),
					    econs_nil(v_pop()))));
		    break;
		default:
		    eread_error(null, "expecting 'expression+ ;'");
	    }
	    break;
	case tok_struct: case tok_class: case tok_union:
	    object = econs_nil(v_top());
	    v_top() = object;
	    if (structure(token) == tok_declaration) {
		object = v_pop();
		v_top() = object;
		/* type tag already checked */
		token = tok_declaration;
		goto declaration;
	    }
	    erplacd(object, econs_nil(econs_nil(v_pop())));
	    switch (lookahead()) {
		case tok_brace:
		    /* definition */
		    statement();
		    if (ecdr(v_top()) == null)
			/* struct name {} */
			eread_error(null, "empty declaration");
		    erplacd(ecadr(object), ecdr(v_pop()));
		    if (lookahead() != tok_progn)
			goto struct_declaration;
		    /* FALLTHROUGH */
		case tok_progn:
		    /* declaration */
		    consume();
		    break;
		case tok_comma:
		    /* special case for declaration in format:
		     *	class foo, bar, baz;
		     * where the extra declarations are not appended,
		     * just declare the type, as extra data is assumed
		     * to be instance declarations by ecode.c:record_statement
		     */
		    do {
			consume();
			structure(token);
			if (!etag_p(v_top()))
			    eread_error(null, "expecting 'expression+ ;'");
			v_dec();
		    } while (lookahead() == tok_comma);
		    if (lookahead() != tok_progn)
			eread_error(null, "expecting 'expression+ ;'");
		    consume();
		    break;
		default:
		struct_declaration:
		    if (expression_group(tok_code) != tok_progn)
			eread_error(null, "expecting 'expression+ ;'");
		    consume();
		    erplacd(ecdr(object), v_pop());
		    break;
	    }
	    break;
	case tok_brace:
	    object = econs_nil(efixnum(tok_code));
	    v_top() = object;
	    while (lookahead() != tok_ebrace) {
		statement_noeof();
		erplacd(object, econs_nil(v_pop()));
		object = ecdr(object);
	    }
	    consume();
	    break;
	case tok_progn:
	    object = econs_nil(v_top());
	    v_top() = object;
	    break;
	case tok_if:
	    object = econs_nil(v_top());
	    v_top() = object;
	    /* test */
	    if (primary_noeof() != tok_list)
		eread_error(null, "expecting '('");
	    v_dec();
	    if (expression_group(tok_code) != tok_elist)
		eread_error(null, "expecting 'expression+ )'");
	    consume();
	    erplacd(object, econs_nil(v_pop()));
	    /* statement */
	    statement_noeof();
	    object = ecdr(object);
	    erplacd(object, econs_nil(v_pop()));
	    if (lookahead() == tok_else) {
		/* associate "else" with last "if" */
		consume();
		statement_noeof();
		object = ecdr(object);
		erplacd(object, econs_nil(v_pop()));
	    }
	    break;
	case tok_while:
	    object = econs_nil(v_top());
	    v_top() = object;
	    /* test */
	    if (primary_noeof() != tok_list)
		eread_error(null, "expecting '('");
	    v_dec();
	    if (expression_group(tok_code) != tok_elist)
		eread_error(null, "expecting 'expression+ )'");
	    consume();
	    erplacd(object, econs_nil(v_pop()));
	    /* statement */
	    statement_noeof();
	    object = ecdr(object);
	    erplacd(object, econs_nil(v_pop()));
	    break;
	case tok_for:
	    object = econs_nil(v_top());
	    v_top() = object;
	    /* init */
	    if (primary_noeof() != tok_list)
		eread_error(null, "expecting '('");
	    v_dec();
	    if (expression_group(tok_code) != tok_progn)
		eread_error(null, "expecting 'expression+ ;'");
	    consume();
	    erplacd(object, econs_nil(v_pop()));
	    /* test */
	    if (expression_group(tok_code) != tok_progn)
		eread_error(null, "expecting 'expression+ ;'");
	    consume();
	    object = ecdr(object);
	    erplacd(object, econs_nil(v_pop()));
	    /* update */
	    if (expression_group(tok_code) != tok_elist)
		eread_error(null, "expecting 'expression+ )'");
	    consume();
	    object = ecdr(object);
	    erplacd(object, econs_nil(v_pop()));
	    /* code */
	    statement_noeof();
	    object = ecdr(object);
	    erplacd(object, econs_nil(v_pop()));
	    break;
	case tok_do:
	    object = econs_nil(v_top());
	    v_top() = object;
	    /* code */
	    statement_noeof();
	    erplacd(object, econs_nil(v_pop()));
	    /* while */
	    if (primary_noeof() != tok_while)
		eread_error(null, "expecting 'while'");
	    v_dec();
	    /* test */
	    if (primary_noeof() != tok_list)
		eread_error(null, "expecting '('");
	    v_dec();
	    if (expression_group(tok_code) != tok_elist)
		eread_error(null, "expecting 'expression+ )'");
	    consume();
	    object = ecdr(object);
	    erplacd(object, econs_nil(v_pop()));
	    if (primary_noeof() != tok_progn)
		eread_error(null, "expecting ';'");
	    v_dec();
	    break;
	case tok_switch:
	    object = econs_nil(v_top());
	    v_top() = object;
	    /* test */
	    if (primary_noeof() != tok_list)
		eread_error(null, "expecting '('");
	    v_dec();
	    if (expression_group(tok_code) != tok_elist)
		eread_error(null, "expecting 'expression+ )'");
	    consume();
	    erplacd(object, econs_nil(v_pop()));
	    /* statement() will use tok_brace */
	    if (lookahead() != tok_brace)
		eread_error(null, "expecting '{'");
	    /* cases */
	    statement_noeof();
	    object = ecdr(object);
	    erplacd(object, econs_nil(v_pop()));
	    break;
	case tok_case:
	    object = econs_nil(v_top());
	    v_top() = object;
	    switch (expression_noeof()) {
		case tok_label:
		    erplacd(object, ecadr(v_pop()));
		    break;
		case tok_type:
		    einteger(&ecdr(object), etag_to_integer(v_pop()));
		    if (lookahead() != tok_collon)
			goto case_fail;
		    consume();
		    break;
		case tok_typeof:
		    erplacd(object, ecadr(v_pop()));
		    if (ecdr(object) == null || etag_p(ecdr(object)))
			einteger(&ecdr(object), etag_to_integer(ecdr(object)));
		    else
			goto case_fail;
		    if (lookahead() != tok_collon)
			goto case_fail;
		    consume();
		    break;
		    /* number...number */
		case tok_number:
		    erplacd(object, econs_nil(v_pop()));
		    if (!eintfix_p(ecadr(object)))
			eread_error(null,
				    "'case' value is not a fixed precision integer");
		    if (primary_noeof() != tok_ellipsis)
			goto case_fail;
		    v_dec();
		    if (expression_noeof() != tok_label)
			goto case_fail;
		    object = ecdr(object);
		    erplacd(object, ecadr(v_pop()));
		    break;
		default:
		case_fail:
		    eread_error(null, "expecting label after 'case'");
	    }
	    if (!einteger_p(ecdr(object)))
		eread_error(null, "'case' value is not an integer");
	    break;
	case tok_default:
	    object = econs_nil(v_top());
	    v_top() = object;
	    if (primary_noeof() != tok_collon)
		eread_error(null, "expecting ':'");
	    v_dec();
	    break;
	case tok_break:
	case tok_continue:
	    object = econs_nil(v_top());
	    v_top() = object;
	    if (primary_noeof() != tok_progn)
		eread_error(null, "expecting ';'");
	    v_dec();
	    break;
	case tok_goto:
	    object = econs_nil(v_top());
	    v_top() = object;
	    if (unary_noeof() != tok_symbol)
		eread_error(null, "expecting label");
	    erplacd(object, econs_nil(v_pop()));
	    if (primary_noeof() != tok_progn)
		eread_error(null, "expecting ';'");
	    v_dec();
	    break;
	case tok_return:
	    object = econs_nil(v_top());
	    v_top() = object;
	    if (lookahead() != tok_progn) {
		expression_rvalue();
		erplacd(object, econs_nil(v_pop()));
		if (primary_noeof() != tok_progn)
		    eread_error(null, "expecting ';'");
		v_dec();
	    }
	    else
		consume();
	    break;
	case tok_typedef:
	    object = econs_nil(v_top());
	    v_top() = object;
	    if (primary_noeof() != tok_type)
		eread_error(null, "expecting type");
	    erplacd(object, econs_nil(v_pop()));
	    if (expression_group(tok_code) != tok_progn)
		eread_error(null, "expecting 'expression+ ;'");
	    erplacd(ecdr(object), v_pop());
	    consume();
	    break;
	case tok_try:
	    object = econs_nil(v_top());
	    v_top() = object;
	    statement_noeof();
	    erplacd(object, econs_nil(v_pop()));
	    /* reserve space for 'always' block */
	    object = ecdr(object);
	    erplacd(object, econs_nil(null));
	    append = ecdr(object);
	    for (;;) {
		switch (lookahead()) {
		    case tok_always:
			consume();
			if (ecar(append))
			    eread_error(null, "duplicate 'always'");
			statement_noeof();
			erplaca(append, v_pop());
			break;
		    case tok_catch:
			consume();
			if (primary_noeof() != tok_list)
			    eread_error(null, "expecting '('");
			v_dec();
			unary_noeof();
			object = ecdr(object);
			erplacd(object, econs_nil(econs_nil(v_pop())));
			if (primary_noeof() != tok_elist)
			    eread_error(null, "expecting 'expression+ )'");
			v_dec();
			statement_noeof();
			erplacd(ecadr(object), econs_nil(v_pop()));
			break;
		    default:
			goto done;
		}
	    }
	    break;
	case tok_catch:
	    eread_error(null, "'catch' must follow 'try'");
	case tok_throw:
	    object = econs_nil(v_top());
	    v_top() = object;
	    if (lookahead() != tok_progn) {
		expression_rvalue();
		erplacd(object, econs_nil(v_pop()));
		if (primary_noeof() != tok_progn)
		    eread_error(null, "expecting ';'");
		v_dec();
	    }
	    else
		consume();
	    break;
	case tok_symbol:
	case tok_number:
	case tok_expression:
	case tok_string:
	case tok_function:
	case tok_pointer:
	case tok_address:
	    object = econs(null, econs_nil(v_top()));
	    v_top() = object;
	    switch (lookahead()) {
		case tok_comma:
		    consume();
		    if (expression_group(tok_code) != tok_progn)
			eread_error(null, "expecting 'expression+ ;'");
		    erplacd(ecdr(object), v_pop());
		    /* FALLTHROUGH */
		case tok_progn:
		    primary();
		    erplaca(object, v_pop());
		    break;
		default:
		    eread_error(null, "expecting 'expression* ;'");
	    }
	    break;
	default:
	    eread_error(null, "expecting 'expression* ;'");
    }

done:
    annotate(line, name, v_top());

    return (token);
}

static etoken_t
expression(void)
{
    etoken_t	token;
    eobject_t	object;

    token = precedence();
    switch (token) {
	case tok_number:
	case tok_symbol:
	    if (lookahead() == tok_collon) {
		/* label name */
		object = econs(efixnum(tok_label),
			       econs_nil(v_top()));
		v_top() = object;
		consume();			/* ':' */
		token = tok_label;
	    }
	    break;
	default:
	    break;
    }

    return (token);
}

static etoken_t
precedence(void)
{
    etoken_t	 next;
    etoken_t	 token;
    eobject_t	 object;

    token = unary();
    for (;;) {
	switch (token) {
	    case tok_brace:
		object = econs_nil(efixnum(tok_data));
		v_top() = object;
		annotate(input->line, input->name, object);
		if (expression_group(tok_data) != tok_ebrace)
		    eread_error(null, "expecting 'expression* }'");
		erplacd(object, v_pop());
		consume();			/* '}' */
		return (tok_expression);
	    case tok_declaration:
		if (lookahead() == tok_set) {
		    /* format object to have type first */
		    object = v_top();
		    v_push(ecadr(object));
		    binary_assign();
		    erplacd(object, econs_nil(v_pop()));
		}
		return (tok_declaration);
	    case tok_number:
	    case tok_symbol:
	    case tok_expression:
	    case tok_function:
	    case tok_pointer:
	    case tok_typeof:
	    case tok_string:
		next = ekind_of(lookahead());
		switch (next) {
		    case tok_set:
			token = binary_assign();
			break;
		    case tok_logop:
		    case tok_logcmp:
		    case tok_bitop:
		    case tok_shift:
		    case tok_addsub:
		    case tok_muldivrem:
			token = binary_left(next);
			break;
		    case tok_ternary:
			/* test expression in stack */
			object = econs(null, econs_nil(v_top()));
			v_top() = object;
			primary();		/* '?' */
			erplaca(object, v_pop());
			/* read true condition */
			precedence_noeof();
			object = ecdr(object);
			erplacd(object, econs_nil(v_pop()));
			if (primary_noeof() != tok_collon)
			    eread_error(null, "expecting ':'");
			v_dec();		/* ':' */
			/* read false condition */
			precedence_noeof();
			object = ecdr(object);
			erplacd(object, econs_nil(v_pop()));
			token = tok_expression;
			break;
		    case tok_incdec:
			token = primary();
			v_dec();
			token = token == tok_inc ? tok_postinc : tok_postdec;
			object = econs(efixnum(token), econs_nil(v_top()));
			v_top() = object;
			token = tok_expression;
			break;
		    default:
			return (token);
		}
		break;
	    default:
		return (token);
	}
    }
}

static etoken_t
expression_group(etoken_t state)
{
    eobject_t	cons;
    etoken_t	token;
    eobject_t	object;

    object = null;
    token = lookahead();
    switch (token) {
	case tok_eof:
	    eread_error(null, "unexpected end of file");
	case tok_ebrace:
	case tok_elist:
	case tok_progn:
	    v_check(1);
	    v_push(null);
	    return (token);
	default:
	    for (;;) {
		switch (lookahead()) {
		    case tok_ebrace:
			/* if reading a data block, ignore an extra ',' */
			if (state == tok_data)
			    return (tok_ebrace);
			eread_error(null, "unexpected '}'");
		    case tok_elist:
			eread_error(null, "unexpected ')'");
		    case tok_progn:
			eread_error(null, "unexpected ';'");
		    case tok_struct:	case tok_union:
			/* FIXME this should be shared with toplevel
			 * evaluation of structs and unions,
			 * in which case, it would also not trigger
			 * an error if the first reference to it
			 * is in a prototype, that is, automatically
			 * declare it when parsing a prototype */
			primary();
			cons = econs_nil(v_top());
			v_top() = cons;
			token = expression_noeof();
			cons = econs_nil(tmain->egs[tmain->gsi - 1]);
			erplacd(tmain->egs[tmain->gsi - 2], cons);
			v_dec();
			break;
		    default:
			token = expression_noeof();
			if (token == tok_label && state == tok_data) {
			    erplaca(v_top(), efixnum(tok_offset));
			    expression_rvalue();
			    cons = econs_nil(tmain->egs[tmain->gsi - 1]);
			    erplacd(ecdr(tmain->egs[tmain->gsi - 2]), cons);
			    v_dec();
			}
			break;
		}
		if (object == null) {
		    object = econs_nil(v_top());
		    v_top() = object;
		}
		else {
		    erplacd(object, econs_nil(v_pop()));
		    object = ecdr(object);
		}
		if ((token = lookahead()) == tok_comma)
		    consume();
		else
		    break;
	    }
	    break;
    }

    return (token);
}

/* right associative:
 *	=, *=, /=, %=, +=, -=, <<=, >>=, &=, |=, ^=
 */
static etoken_t
binary_assign(void)
{
    etoken_t	token;
    eobject_t	object;
    eobject_t	keyword;

    do {
	/* read operator */
	token = primary();
	/* parse assigned value */
	switch (expression_noeof()) {
	    case tok_address:
		if (token != tok_set)
		    eread_error(null, "expecting expression");
		break;
	    case tok_number:
	    case tok_symbol:
	    case tok_string:
	    case tok_function:
	    case tok_expression:
	    case tok_pointer:
		break;
	    default:
		eread_error(null, "expecting expression");
	}
	object = econs_nil(v_pop());
	keyword = v_pop();
	object = econs(v_top(), object);
	v_top() = econs(keyword, object);
    } while (ekind_of(lookahead()) == tok_set);

    return (tok_expression);
}

/* left associative:
 *	&&, ||
 *	!=, <=, <, ==, >=, >
 *	&, |, ^	
 *	<<, >>
 *	+, -
 *	*, /, %
 */
static etoken_t
binary_left(etoken_t token)
{
    etoken_t	next;
    eobject_t	object;
    eobject_t	keyword;

    do {
	/* read operator */
	primary();
	/* parse right side of operation */
	binary_right(token);
	object = econs_nil(v_pop());
	keyword = v_pop();
	object = econs(v_top(), object);
	v_top() = econs(keyword, object);
	tokenize();
	next = ekind_of(lookahead());
    } while (next >= token && next <= tok_muldivrem);

    return (tok_expression);
}

static void
binary_right(etoken_t token)
{
    etoken_t	next;
    eobject_t	object;

    unary_rvalue();
    for (;;) {
	next = ekind_of(lookahead());
	/* if next operation has higher precedence */
	if (next > token && next <= tok_muldivrem) {
	    /* one object already pushed on stack by unary_rvalue() */
	    binary_left(next);
	    next = ekind_of(lookahead());
	    if (next < token || next > tok_muldivrem)
		break;
	}
	else if (next == tok_incdec) {
	    next = primary();
	    v_dec();
	    next = next == tok_inc ? tok_postinc : tok_postdec;
	    object = econs(efixnum(next), econs_nil(v_top()));
	    v_top() = object;
	}
	else
	    break;
    }
}

static etoken_t
unary(void)
{
    etag_t	*tag;
    eobject_t	 cons;
    etoken_t	 next;
    etoken_t	 token;
    eobject_t	 object;
    ebool_t	 parenthesis;

    token = primary();
    switch (token) {
	case tok_type:
	    switch (lookahead()) {
		case tok_mul:
		    object = econs_nil(v_top());
		    v_top() = object;
		    v_push(null);
		    consume();
		    token = unary_pointer();
		    switch (token) {
			case tok_elist:
			case tok_comma:
			case tok_progn:
			case tok_collon:
			    v_dec();
			    cons = etag_pointer(ecar(object));
			    v_top() = cons;
			    token = tok_type;
			    goto done;
			default:
			    goto type;
		    }
		case tok_list:
		    object = econs_nil(v_top());
		    v_top() = object;
		    /* function type object */
		    unary();
		    if (ecdr(v_top()) == null) {
			tag = ecar(object);
			if (tag && etag_p(tag) && tag->type == tag_class) {
			    /* constructor: type(); or type() {...} */
			    v_dec();
			    cons = econs(efixnum(tok_ctor), object);
			    v_top() = cons;
			    token = tok_ctor;
			    goto done;
			}
		    }
		    goto type;
		case tok_symbol:
		    object = econs_nil(v_top());
		    v_top() = object;
		    primary();
		type:
		    for (;;) {
			switch (lookahead()) {
			    case tok_list:
				consume();
				unary_list();
				echeck_void(v_top());
				break;
			    case tok_vector:
				unary_vector();
				break;
			    default:
				goto declare;
			}
		    }
		declare:
		    erplacd(object, econs_nil(v_pop()));
		    token = tok_declaration;
		    break;
		case tok_vector:
		    token = unary_vector();
		    /* prototypes cannot declare dynamically sized vectors,
		     * so, tag vector object here, to also not cause an
		     * error on prototypes in the format:
		     *	T name(T[...]);
		     */
		    next = lookahead();
		    switch (next) {
			case tok_elist:
			case tok_comma:
			    if (dynamic_vector_p(ecddr(v_top())))
				break;
			    /* for 'case' on typeof() */
			case tok_collon:
			    object = ecdr(v_top());
			    cons = etag_vector(ecar(object), ecdr(object));
			    v_top() = cons;
			    token = tok_type;
			    break;
			default:
			    break;
		    }
		    break;
		case tok_brace:
		    /* support for the sytax:
		     *     class T a = new T { field: value };
		     * FIXME this needs some work for union objects.
		     */
		    if ((tag = v_top()) && etag_p(tag) &&
			(tag->type == tag_class || tag->type == tag_struct) &&
			tmain->gsi > 1 &&
			econs_p(tmain->egs[tmain->gsi - 2]) &&
			ecar(tmain->egs[tmain->gsi - 2]) == efixnum(tok_new) &&
			!(((erecord_t *)tag->data)->type & t_pointer)) {
			consume();
			object = econs(efixnum(tok_new_type_data),
				       econs_nil(tag));
			v_top() = object;
			annotate(input->line, input->name, object);
			if (expression_group(tok_data) != tok_ebrace)
			    eread_error(null, "expecting 'expression* }'");
			erplacd(ecdr(object), v_pop());
			consume();
			token = tok_expression;
		    }
		    break;
		case tok_type:
		    /* type class.method() */
		    object = econs_nil(v_top());
		    v_top() = object;
		    /* FIXME the proper call should be unary()
		     * but only to remove extra parenthesis, as
		     * in 'type (class).method()', as a type specifier
		     * other then a class tag is an error */
		    primary();
		    if (lookahead() == tok_field)
			unary_field();
		    else
			eread_error(null, "expecting 'expression*'");
		    goto type;
		case tok_field:
		    /* method call */
		    token = unary_field();
		    goto pointer;
		default:
		    break;
	    }
	    break;
	case tok_list:
	    token = unary_list();
	    echeck_void(v_top());
	    if ((object = ecdr(v_top())) == null) {	/* () */
		if (tmain->gsi > 1 && econs_p(tmain->egs[tmain->gsi - 2])) {
		    tag = ecar(tmain->egs[tmain->gsi - 2]);
		    if (tag && etag_p(tag) && tag->type == tag_class) {
			/* don't cause an error in ctor declaration/defintion */
			token = tok_expression;
			goto done;
		    }
		}
		eread_error(null, "unexpected '()'");
	    }
	    if (ecdr(object) == null) {
		v_top() = ecar(object);
		switch ((token = tokenize())) {
		    case tok_symbol:
			goto pointer;
		    case tok_expression:
			if (ecar(v_top()) == efixnum(tok_pointer) ||
			    ecar(v_top()) == efixnum(tok_new))
			    goto pointer;
		    default:
			break;
		}
	    }
	    if (lookahead() == tok_vector)
		token = unary_vector();
	    break;
	case tok_symbol:
	pointer:
	    for (;;) {
		switch (lookahead()) {
		    case tok_vector:
			token = unary_vector();
			break;
		    case tok_list:
			/* function call */
			consume();
			unary_list();
			token = tok_function;
			break;
		    case tok_field:
			token = unary_field();
			break;
		    default:
			goto done;
		}
	    }
	    break;
	case tok_add:
	    unary_rvalue();
	    object = v_pop();
	    v_top() = object;
	    token = tokenize();
	    break;
	case tok_sub:
	    unary_rvalue();
	    object = econs(efixnum(tok_neg), econs_nil(v_pop()));
	    v_top() = object;
	    token = tokenize();
	    break;
	case tok_mul:
	    if ((token = unary_pointer()) != tok_expression)
		eread_error(null, "expecting expression");
	    goto pointer;
	case tok_and:
	    object = econs_nil(efixnum(tok_address));
	    v_top() = object;
	    unary_rvalue();
	    erplacd(object, econs_nil(v_pop()));
	    token = tok_address;
	    break;
	case tok_atan2 ... tok_complex:
	case tok_subtypeof:
	case tok_renew:
	    object = econs_nil(v_top());
	    v_top() = object;
	    if (lookahead() != tok_list)
		eread_error(null, "expecting '('");
	    consume();
	    switch (expression_noeof()) {
		case tok_pointer:
		case tok_address:
		case tok_type:
		    if (token != tok_subtypeof)
			eread_error(null,
				    "expecting 'expression, expression )'");
		case tok_string:
		case tok_number:
		case tok_symbol:
		case tok_expression:
		case tok_function:
		    erplacd(object, econs_nil(v_pop()));
		    break;
		default:
		    eread_error(null, "expecting 'expression, expression )'");
	    }
	    if (lookahead() != tok_comma)
		eread_error(null, "expecting ','");
	    consume();
	    object = ecdr(object);
	    switch (expression_noeof()) {
		case tok_pointer:
		case tok_address:
		case tok_type:
		    if (token != tok_subtypeof)
			eread_error(null,
				    "expecting 'expression, expression )'");
		case tok_string:
		case tok_number:
		case tok_symbol:
		case tok_expression:
		case tok_function:
		    erplacd(object, econs_nil(v_pop()));
		    break;
		default:
		    eread_error(null, "expecting 'expression )'");
	    }
	    if (lookahead() != tok_elist)
		eread_error(null, "expecting ')'");
	    consume();
	    if (token != tok_subtypeof && token != tok_renew)
		token = tokenize();
	    else
		token = tok_expression;
	    break;
	case tok_inc:
	case tok_dec:
	case tok_not:
	case tok_com:
	    object = econs_nil(v_top());
	    v_top() = object;
	    unary_rvalue();
	    erplacd(object, econs_nil(v_pop()));
	    token = tokenize();
	    break;
	case tok_signbit ... tok_log10:
	    object = econs_nil(v_top());
	    v_top() = object;
	    if (lookahead() != tok_list)
		eread_error(null, "expecting '('");
	    consume();
	    switch (expression_noeof()) {
		default:
		    if (v_top())
			/* allow explicit null as a 0 */
			eread_error(null, "expecting 'expression )'");
		case tok_number:
		case tok_symbol:
		case tok_expression:
		case tok_function:
		    erplacd(object, econs_nil(v_pop()));
		    break;
	    }
	    if (lookahead() != tok_elist)
		eread_error(null, "expecting ')'");
	    consume();
	    token = tokenize();
	    break;
	case tok_new:
	case tok_sizeof:
	case tok_typeof:
	case tok_rankof:
	    object = econs_nil(v_top());
	    v_top() = object;
	    parenthesis = lookahead() == tok_list;
	    if (parenthesis)
		consume();
	    switch (parenthesis ? expression_noeof() : unary_noeof()) {
		case tok_number:
		case tok_symbol:
		case tok_expression:
		case tok_string:
		case tok_function:
		case tok_pointer:
		case tok_address:
		case tok_type:
		    erplacd(object, econs_nil(v_pop()));
		    break;
		case tok_ellipsis:
		    if (token == tok_sizeof || token == tok_rankof) {
			erplacd(object, econs_nil(v_pop()));
			break;
		    }
		    /* FALLTHROUGH */
		default:
		    eread_error(null, "expecting expression");
	    }
	    if (parenthesis) {
		if (lookahead() != tok_elist) {
		    if (token != tok_sizeof || lookahead() != tok_comma)
			eread_error(null, "expecting 'expression )'");
		    else {
			consume();
			(void)expression_rvalue();
			erplacd(ecdr(object), econs_nil(v_pop()));
			if (lookahead() != tok_elist)
			    eread_error(null, "expecting 'sizeof(expression)'");
		    }
		}
		consume();
	    }
	    if (token != tok_typeof) {
		if (token == tok_new && lookahead() == tok_field)
		    goto pointer;
		token = tok_expression;
	    }
	    break;
	case tok_ellipsis:
	    v_top() = ellipsis_obj;
	    if (lookahead() == tok_vector)
		token = unary_vector();
	    break;
	case tok_string:
	    token = unary_string();
	    break;
	case tok_this:
	    if (lookahead() == tok_field) {
		unary_field();
		v_top() = ecdr(v_top());
		erplaca(v_top(), efixnum(tok_this));
		token = tok_expression;
		goto pointer;
	    }
	    else
		v_top() = this_obj;
	    token = tok_expression;
	    break;
	case tok_FUNCTION:
	    v_top() = function_obj;
	    /* not usable on concatenation, because it is resolved in ecode.c */
	    token = tok_string;
	    break;
	default:
	    break;
    }

done:
    return (token);
}

static ebool_t
dynamic_vector_p(eobject_t list)
{
    eobject_t	object;

    for (; list; list = ecdr(list)) {
	object = ecar(list);
	if (object && !efixnum_p(object))
	    return (true);
    }

    return (false);
}

static etoken_t
unary_vector(void)
{
    etoken_t	token;
    eobject_t	object;
    eobject_t	vector;

    vector = econs_nil(lah_value);
    object = v_top();
    v_top() = vector;
    erplacd(vector, econs_nil(object));
    vector = ecdr(vector);
    do {
	consume();				/* '[' */
	if (expression_noeof() == tok_evector) {
	    v_dec();
	    object = econs_nil(null);
	}
	else if (primary_noeof() != tok_evector)
	    eread_error(null, "expecting ']'");
	else {
	    v_dec();
	    object = econs_nil(v_pop());
	}
	erplacd(vector, object);
	vector = ecdr(vector);
	token = lookahead();
    } while (token == tok_vector);

    return (tok_expression);
}

static etoken_t
unary_field(void)
{
    etoken_t	token;
    eobject_t	object;
    eobject_t	keyword;

    /* left associative */
    do {
	primary();				/* '.' */
	if (lookahead() == tok_type) {
	    token = unary_noeof();
	    object = v_pop();
	    switch (token) {
		case tok_ctor:
		    v_dec();			/* '.' */
		    if (tmain->gsi < 1 || v_top() != ecadr(object))
			eread_error(null, "expecting constructor");
		    v_top() = object;
		    return tok_ctor;
		case tok_function:
		    /* explicit class method call */
		    v_top() = efixnum(tok_explicit);
		    break;
		default:
		    eread_error(null, "expecting method call");
	    }
	}
	else if (primary_noeof() == tok_symbol)	/* field */
	    object = econs_nil(v_pop());
	else
	    eread_error(null, "expecting symbol");
	keyword = v_pop();
	object = econs(v_top(), object);	/* record */
	v_top() = econs(keyword, object);
	token = lookahead();
    } while (token == tok_field);

    return (tok_expression);
}

static etoken_t
unary_list(void)
{
    eobject_t	object;

    object = econs_nil(v_top());
    v_top() = object;
    if (expression_group(tok_code) != tok_elist)
	eread_error(null, "expecting 'expression* )'");
    erplacd(object, v_pop());
    consume();					/* ')' */

    return (tok_expression);
}

static etoken_t
unary_pointer(void)
{
    etoken_t	token;
    eobject_t	object;

    object = econs_nil(efixnum(tok_pointer));
    v_top() = object;
    for (token = lookahead(); token == tok_mul; token = lookahead()) {
	consume();				/* '*' */
	erplacd(object, econs_nil(econs_nil(efixnum(tok_pointer))));
	object = ecadr(object);
    }
    switch (token) {
	case tok_elist:
	case tok_comma:
	case tok_progn:
	case tok_collon:
	    break;
	default:
	    unary_rvalue();
	    erplacd(object, econs_nil(v_pop()));
	    token = tok_expression;
	    break;
    }

    return (token);
}

static etoken_t
unary_string(void)
{
    etoken_t	 token;
    ebool_t	 first;
    evector_t	*string;
    evector_t	*append;
    eint32_t	 length;
    eint32_t	 offset;

    /* constant null is tokenized as tok_string */
    if ((string = v_top())) {
	first = true;
	offset = string->length;
	while ((token = lookahead()) == tok_string) {
	    primary();
	    if ((append = v_top()) == null)
		eparse_error(null, "syntax error");
	    length = offset + append->length;
	    /* if integer overflow or string too large */
	    if (length < offset || length > most_positive_fix32)
		eparse_error(null, "out of bounds");
	    if (first) {
		evector(&append, t_uint8, length, 1);
		memcpy(append->v.obj, string->v.obj, string->length);
		tmain->egs[tmain->gsi - 2] = string = append;
		append = v_top();
		first = false;
	    }
	    else
		erenew_vector(string, length);
	    memcpy(string->v.u8 + offset, append->v.u8, append->length);
	    offset = length;
	    v_dec();
	}
	if (!first)
	    v_top() = eget_string(string->v.obj, string->length);
	token = token == tok_vector ? unary_vector() : tok_string;
    }
    else
	token = tok_string;

    return (token);
}

static etoken_t
primary(void)
{
    etoken_t	 token;
    eobject_t	 object;

    v_check(1);
    if (lah_token != tok_eof) {
	token = lah_token;
	lah_token = tok_eof;
	v_push(lah_value);
	return (token);
    }

    if ((object = eread_object()) == object_eof)
	return (tok_eof);

    v_push(object);

    return (tokenize());
}

static etoken_t
tokenize(void)
{
    evalue_t	 l, r;
    etoken_t	 token;
    esymbol_t	*symbol;
    eobject_t	 object;
    eobject_t	 keyword;
    v_enter();

    object = v_top();
again:
    switch (etype(object) & ~t_const) {
	case t_fixnum:	case t_int:	case t_float:
	case t_mpz:	case t_mpq:	case t_mpr:	case t_mpc:
	case t_cdd:	case t_cqq:
	    token = tok_number;
	    break;
	    /* FIXME not really (must have come here from 'null' symbol)
	     * need to check if 'null' cannot be used on wrong syntax
	     * but there are still way too much wrong syntax that will
	     * be accepted.
	     * also, initially the idea was to implement enums just as
	     * special symbols, but if having them parsed here, it will
	     * make it not possible to do things like warning when
	     * not all enums are tested in a 'switch', but, 'enum'
	     * is also still a TODO, that may be implemented in a
	     * different way.
	     */
	case t_void:
	case t_string:
	    token = tok_string;
	    break;
	case t_symbol:
	    symbol = object;
	    if (symbol->a_token) {
		v_top() = symbol->value;
		token = eget_fixnum(symbol->value);
	    }
	    else if (symbol->a_type) {
		v_top() = symbol->value;
		token = tok_type;
	    }
	    else if (symbol->a_special) {
		v_top() = object = symbol->value;
		goto again;
	    }
	    else
		token = tok_symbol;
	    break;
	case t_cons:
	    keyword = ecar(object);
	    if (!efixnum_p(keyword)) {
		/* function call inside redundant parenthesis */
		token = tok_expression;
		goto done;
	    }

	    token = tok_expression;
	    object = ecdr(object);
	    if (object == null || !enumber_get(ecar(object), &l))
		goto done;
	    object = ecdr(object);
	    if (econs_p(object)) {
		if (object == null || !enumber_get(ecar(object), &r))
		    goto done;

		switch (eget_fixnum(keyword)) {
		    /* shift operands are not coerced */
		    case tok_mul2:	case tok_div2:
		    case tok_shl:	case tok_shr:
			break;
		    case tok_complex:
			/* dont coerce type if zero imag part */
			if (efalse_p(ecar(object))) {
			    token = tok_number;
			    goto done;
			}
		    default:
			v_check(2);
			enumber_coerce(&l, &r);
			switch (l.t) {
			    case t_mpz: case t_mpq: case t_mpr:
			    case t_cqq:	case t_mpc:
				v_push(l.v.o);
				v_push(r.v.o);
			    default:
				break;
			}
			break;
		}
	    }

	    if (sigsetjmp(thread_self->env, 1))
		token = tok_expression;
	    else
		token = tokenize_eval(eget_fixnum(keyword), &l, &r);
	    /* restore previous exception */
	    memcpy(&thread_self->env, &cfg_env, sizeof(sigjmp_buf));
	    v_leave();
	    if (token == tok_number)
		v_top() = enumber_create(&l);
	    break;
	case t_tag:
	    token = tok_type;
	    break;
	default:
	    token = tok_eof;
#if DEBUG
	    eread_error(null, "internal error (unexpected object type)");
#else
	    break;
#endif
    }

done:
    return (token);
}

static etoken_t
tokenize_eval(etoken_t token, evalue_t *arg0, evalue_t *arg1)
{
    /* FIXME this "simplification pass" may need to be removed
     * for some operations,  when/if associative/symbolic
     * optimization/simplification is implemented */
    switch (token) {
	case tok_andand:	enumber_andand(arg0, arg1);	break;
	case tok_oror:		enumber_oror(arg0, arg1);	break;
	case tok_lt:		enumber_lt(arg0, arg1);		break;
	case tok_le:		enumber_le(arg0, arg1);		break;
	case tok_ge:		enumber_ge(arg0, arg1);		break;
	case tok_gt:		enumber_gt(arg0, arg1);		break;
	case tok_ne:		enumber_ne(arg0, arg1);		break;
	case tok_eq:		enumber_eq(arg0, arg1);		break;
	case tok_and:		einteger_and(arg0, arg1);	break;
	case tok_or:		einteger_or(arg0, arg1);	break;
	case tok_xor:		einteger_xor(arg0, arg1);	break;
	case tok_mul2:		enumber_mul2(arg0, arg1);	break;
	case tok_div2:		enumber_div2(arg0, arg1);	break;
	case tok_shl:		enumber_shl(arg0, arg1);	break;
	case tok_shr:		enumber_shr(arg0, arg1);	break;
	case tok_add:		enumber_add(arg0, arg1);	break;
	case tok_sub:		enumber_sub(arg0, arg1);	break;
	case tok_mul:		enumber_mul(arg0, arg1);	break;
	case tok_div:		enumber_div(arg0, arg1);	break;
	case tok_trunc2:	enumber_trunc2(arg0, arg1);	break;
	case tok_rem:		enumber_rem(arg0, arg1);	break;
	case tok_atan2:		enumber_atan2(arg0, arg1);	break;
	case tok_pow:		enumber_pow(arg0, arg1);	break;
	case tok_hypot:		enumber_hypot(arg0, arg1);	break;
	case tok_complex:	enumber_complex(arg0, arg1);	break;
	case tok_neg:		enumber_neg(arg0);		break;
	case tok_com:		einteger_com(arg0);		break;
	case tok_not:		enumber_not(arg0);		break;
	case tok_signbit:	enumber_signbit(arg0);		break;
	case tok_signum:	enumber_signum(arg0);		break;
	case tok_rational:	enumber_rational(arg0);		break;
	case tok_integer_p:	enumber_integer_p(arg0);	break;
	case tok_rational_p:	enumber_rational_p(arg0);	break;
	case tok_float_p:	enumber_float_p(arg0);		break;
	case tok_real_p:	enumber_real_p(arg0);		break;
	case tok_complex_p:	enumber_complex_p(arg0);	break;
	case tok_number_p:	enumber_number_p(arg0);		break;
	case tok_finite_p:	enumber_finite_p(arg0);		break;
	case tok_inf_p:		enumber_inf_p(arg0);		break;
	case tok_nan_p:		enumber_nan_p(arg0);		break;
	case tok_num:		enumber_num(arg0);		break;
	case tok_den:		enumber_den(arg0);		break;
	case tok_real:		enumber_real(arg0);		break;
	case tok_imag:		enumber_imag(arg0);		break;
	case tok_arg:		enumber_arg(arg0);		break;
	case tok_conj:		enumber_conj(arg0);		break;
	case tok_floor:		enumber_floor(arg0);		break;
	case tok_trunc:		enumber_trunc(arg0);		break;
	case tok_round:		enumber_round(arg0);		break;
	case tok_ceil:		enumber_ceil(arg0);		break;
	case tok_abs:		enumber_abs(arg0);		break;
	case tok_sqrt:		enumber_sqrt(arg0);		break;
	case tok_cbrt:		enumber_cbrt(arg0);		break;
	case tok_sin:		enumber_sin(arg0);		break;
	case tok_cos:		enumber_cos(arg0);		break;
	case tok_tan:		enumber_tan(arg0);		break;
	case tok_asin:		enumber_asin(arg0);		break;
	case tok_acos:		enumber_acos(arg0);		break;
	case tok_atan:		enumber_atan(arg0);		break;
	case tok_sinh:		enumber_sinh(arg0);		break;
	case tok_cosh:		enumber_cosh(arg0);		break;
	case tok_tanh:		enumber_tanh(arg0);		break;
	case tok_asinh:		enumber_asinh(arg0);		break;
	case tok_acosh:		enumber_acosh(arg0);		break;
	case tok_atanh:		enumber_atanh(arg0);		break;
	case tok_proj:		enumber_proj(arg0);		break;
	case tok_exp:		enumber_exp(arg0);		break;
	case tok_log:		enumber_log(arg0);		break;
	case tok_log2:		enumber_log2(arg0);		break;
	case tok_log10:		enumber_log10(arg0);		break;
	case tok_inc:
	case tok_dec:
	    eread_error(null, "expecting lvalue");
	default:
	    return (tok_expression);
    }
    return (tok_number);
}
